package com.ingenico.insider.services.impl;

import java.awt.geom.Path2D;
import java.io.IOException;
import java.util.Hashtable;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.ingenico.tools.data.sampling.PathSampler;
import com.ingenico.tools.nio.channel.RandomReadableSampleChannel;

//TODO: have more than one thread (one thread per datachannel)
public class AsynchronousSamplingJobProcessor {
	private static Logger _logger = Logger.getLogger(AsynchronousSamplingJobProcessor.class.getCanonicalName());

	private static Hashtable<Object,LinkedList<SamplingJobOutput>> availablePaths;
	private static LinkedList<SamplingJob> jobsList;

	protected final static int STATUS_WORKING  = 1;
	protected final static int STATUS_IDLE     = 2;

	private static int status = 0;
	private static int maxJobsCount = 0;

	public static final class SamplingJobOutput {
		protected final Path2D path;

		protected SamplingJobOutput (Path2D path) {
			this.path = path;
		}

		public Path2D getPath() {
			return path;
		}
	}

	public static final class SamplingJob {
		protected final PathSampler pathSampler;
		protected final RandomReadableSampleChannel dataChannel;
		protected final double scale;
		protected final int targetLength;
		protected final long offset;

		protected Object source;

		public SamplingJob(PathSampler pathSampler, RandomReadableSampleChannel dataChannel, double scale, int targetLength, long offset) {
			this.pathSampler = pathSampler;
			this.dataChannel = dataChannel;
			this.scale = scale;
			this.targetLength = targetLength;
			this.offset = offset;
		}
		
		public void setSource(Object source) {
			this.source = source;
		}

		public String toString() {
			return "SamplingJob [scale = " + this.scale + ", targetLength = " + this.targetLength + ", offset = " + this.offset + "]";
		}
	}

	protected static class Worker extends Thread implements Runnable {
		@Override
		public void run() {
			SamplingJob currentJob;
			while (true) {
				try {
					try {
						synchronized (jobsList) {
							currentJob = jobsList.removeFirst();
						}
						setStatus(STATUS_WORKING);
						Path2D path = currentJob.pathSampler.sampleToPath(
								currentJob.dataChannel,
								currentJob.scale,
								currentJob.targetLength,
								currentJob.offset
						);
						if ((path != null) && (path.getCurrentPoint() != null) ) {
							_logger.finest("Adding " + path + " to the job results...");
							synchronized (availablePaths) {
								LinkedList<SamplingJobOutput> sourceJobResultsList = availablePaths.get(currentJob.source);
								if (sourceJobResultsList == null) {
									sourceJobResultsList = new LinkedList<SamplingJobOutput>();
									availablePaths.put(currentJob.source, sourceJobResultsList);
								}
								sourceJobResultsList.add(new SamplingJobOutput(path));
							}
						}
					} catch (NoSuchElementException e) {
						// When all jobs have been treated clear the job load
						maxJobsCount = 0;
						Thread.sleep(100);
					}
					setStatus(STATUS_IDLE);
				} catch (IOException ioe) {
					_logger.log(Level.SEVERE, ioe.getMessage(), ioe);
					MessageBoxSupplier.getInstance().showErrorDialog(CurvesSupplier.IO_MESSAGE_KEY, ioe);
				} catch (InterruptedException ie) {
					_logger.log(Level.SEVERE, ie.getMessage(), ie);
					MessageBoxSupplier.getInstance().showErrorDialog(MessageBoxSupplier.GENERAL_ERROR_KEY, ie);
				} catch (Exception e) {
					_logger.log(Level.SEVERE, e.getMessage(), e);
					MessageBoxSupplier.getInstance().showErrorDialog(MessageBoxSupplier.GENERAL_ERROR_KEY, e);
				}
			}
		}

		protected void postJob (SamplingJob job) {
			synchronized (jobsList) {
				jobsList.add(job);
			}
		}

	}

	static protected Worker samplingThreadInstance;

	static public void postJob(Object source, SamplingJob job) {
		if (source == null) {
			return;
		}
		if (job == null) {
			return;
		}
		if (jobsList == null) {
			return;
		}

		job.setSource(source);
		synchronized (jobsList) {
			jobsList.add(job);
			++maxJobsCount;
		}
	}

	static public void clearAllJobResults (Object source) {
		if (source == null) {
			return;
		}
		if (availablePaths == null) {
			return;
		}
		synchronized (availablePaths) {
			availablePaths.remove(source);
		}
	}

	static public void cancelAllJobs (Object source) {
		if (source == null) {
			return;
		}
		if (jobsList == null) {
			return;
		}
		
		synchronized (jobsList) {
			LinkedList<SamplingJob> sourceJobsList = new LinkedList<SamplingJob>();
			for (SamplingJob samplingJob : jobsList) {
				if (samplingJob.source == source) {
					sourceJobsList.add(samplingJob);
				}
			}
			jobsList.removeAll(sourceJobsList);
			maxJobsCount=jobsList.size();
		}
	}

	static public SamplingJobOutput getJobOutput (Object source) throws NoSuchElementException {
		if (availablePaths == null) {
			throw new NoSuchElementException();
		}
		synchronized (availablePaths) {
			LinkedList<SamplingJobOutput> sourceJobResultsList = availablePaths.get(source);
			if (sourceJobResultsList == null) {
				throw new NoSuchElementException();
			}
			return sourceJobResultsList.removeFirst();
		}
	}

	static public boolean isJobOutputAvailable (Object source) {
		synchronized (availablePaths) {
			LinkedList<SamplingJobOutput> sourceJobResultsList = availablePaths.get(source);
			if (sourceJobResultsList == null) {
				return false;
			} else {
				return (sourceJobResultsList.size() > 0);
			}
		}
	}

	// O..1 float load value
	static public float getWorkerLoad () {
		if (jobsList == null) {
			return 0;
		}
		if (jobsList.size() == 0) {
			return 0;
		}
		synchronized (jobsList) {
			return (jobsList.size() / (float)maxJobsCount);
		}
	}

	static synchronized public int getStatus() {
		return status;
	}

	protected static synchronized void setStatus(int s) {
		status = s;
	}

	static {
		availablePaths = new Hashtable<Object, LinkedList<SamplingJobOutput>>();
		jobsList = new LinkedList<SamplingJob>();
		samplingThreadInstance = new Worker();
		samplingThreadInstance.start();
	}
}
